//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2012 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------
#include <gmock/gmock.h>

#include <limits>

#include <sqbind/sqbStackUtils.h>

#include "fixtures/TypeFixture.h"
//----------------------------------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------------------------------
/// boolean stack utils tests
//----------------------------------------------------------------------------------------------------------------------
typedef BaseTypeFixture<bool> BoolStackUtilsTest;

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BoolStackUtilsTest, TestPush)
{
  // actual will always be the opposite of the expected value before sq_getbool is called.
  //
  SQBool actual = SQFalse;

  // test pushing true.
  //
  EXPECT_SQ_SUCCEEDED(m_vm, sqb::Push(m_vm, true));
  EXPECT_EQ(1, sq_gettop(m_vm));
  EXPECT_EQ(OT_BOOL, sq_gettype(m_vm, -1));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_getbool(m_vm, -1, &actual));
  EXPECT_EQ(SQTrue, actual);
  sq_poptop(m_vm);

  // test pushing false.
  //
  EXPECT_SQ_SUCCEEDED(m_vm, sqb::Push(m_vm, false));
  EXPECT_EQ(1, sq_gettop(m_vm));
  EXPECT_EQ(OT_BOOL, sq_gettype(m_vm, -1));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_getbool(m_vm, -1, &actual));
  EXPECT_EQ(SQFalse, actual);
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BoolStackUtilsTest, TestMatch)
{
  // nothing on the stack so match should fail.
  //
#if SQBIND_ASSERTS_ENABLED
  EXPECT_DEATH(sqb::Match(sqb::TypeWrapper<bool>(), m_vm, -1), "");
#endif

  // a boolean on the stack to match should pass.
  //
  sq_pushbool(m_vm, SQTrue);
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<bool>(), m_vm, -1));
  sq_poptop(m_vm);

  // an integer on the stack to match should fail.
  //
  sq_pushinteger(m_vm, 10);
  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<bool>(), m_vm, -1));
  sq_poptop(m_vm);

  // a floating point number on the stack to match should fail.
  //
  sq_pushfloat(m_vm, 0.5f);
  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<bool>(), SquirrelFixture::m_vm, -1));
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BoolStackUtilsTest, TestGet)
{
#if SQBIND_ASSERTS_ENABLED
  // if asserts are on and there is nothing at the index specified then Get should assert.
  //
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<bool>(), m_vm, -1), "");
#endif

  // Get should succeed if there is a boolean on the stack.
  //
  sq_pushbool(m_vm, SQTrue);
  EXPECT_TRUE(sqb::Get(sqb::TypeWrapper<bool>(), m_vm, -1));
  sq_poptop(m_vm);

  sq_pushbool(m_vm, SQFalse);
  EXPECT_FALSE(sqb::Get(sqb::TypeWrapper<bool>(), m_vm, -1));
  sq_poptop(m_vm);

#if SQBIND_ASSERTS_ENABLED
  // Get should die if there is something other than a bool on the stack.
  //
  sq_pushinteger(m_vm, 10);
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<bool>(), m_vm, -1), "");
  sq_poptop(m_vm);
#endif
}

//----------------------------------------------------------------------------------------------------------------------
template<typename TypeParam>
class IntegerStackUtilsTest : public BaseTypeFixture<TypeParam> {};
TYPED_TEST_CASE(IntegerStackUtilsTest, IntegerTypes);

//----------------------------------------------------------------------------------------------------------------------
TYPED_TEST(IntegerStackUtilsTest, TestPush)
{
  // test pushing a randomly generated value
  //
  const TypeParam expected = GetRandomValue();
  EXPECT_SQ_SUCCEEDED(m_vm, sqb::Push(SquirrelFixture::m_vm, expected));

  EXPECT_EQ(1, sq_gettop(SquirrelFixture::m_vm));
  EXPECT_EQ(OT_INTEGER, sq_gettype(SquirrelFixture::m_vm, -1));

  // ensure that the actual value isn't equal to the expected one to start with
  //
  SQInteger actual = static_cast<SQInteger>(expected) - 1;
  EXPECT_SQ_SUCCEEDED(m_vm, sq_getinteger(SquirrelFixture::m_vm, -1, &actual));
  EXPECT_EQ(expected, actual);

  sq_poptop(SquirrelFixture::m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TYPED_TEST(IntegerStackUtilsTest, TestMatch)
{
  // nothing on the stack to match should die.
  //
  #if SQBIND_ASSERTS_ENABLED
    EXPECT_DEATH(sqb::Match(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1), "");
  #endif

  const SQInteger value = static_cast<SQInteger>(GetRandomValue());
  RecordProperty("RandomValue", static_cast<int>(value));

  // an integer on the stack to match should pass.
  //
  sq_pushinteger(m_vm, value);
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1));
  sq_poptop(m_vm);

  // a boolean on the stack to match should fail.
  //
  sq_pushbool(m_vm, SQTrue);
  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1));
  sq_poptop(m_vm);

  // a floating point number on the stack to match should pass.
  //
  sq_pushfloat(m_vm, 0.5f);
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1));
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TYPED_TEST(IntegerStackUtilsTest, TestGet)
{
#if SQBIND_ASSERTS_ENABLED
  // if asserts are on and there is nothing at the index specified then Get should assert.
  //
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1), "");
#endif

  const SQInteger expected = static_cast<SQInteger>(GetRandomValue());
  RecordProperty("RandomValue", static_cast<int>(expected));

  // Get should succeed if there is an integer on the stack.
  //
  sq_pushinteger(m_vm, expected);
  EXPECT_EQ(expected, sqb::Get(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1));
  sq_poptop(m_vm);

  // Get should succeed if there is a float within the correct range on the stack. There are limits as to
  // how well the value can be preserved when converting between TypeParam->SQFloat->SQInteger->TypeParam so
  // this test case casts the same as it is expected to be cast internally in squirrel.
  //
  SQFloat expected_float = static_cast<SQFloat>(expected);
  sq_pushfloat(m_vm, expected_float);
  EXPECT_EQ(static_cast<SQInteger>(expected_float), sqb::Get(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1));
  sq_poptop(m_vm);

#if SQBIND_ASSERTS_ENABLED
  // Get should assert if there is something other than an integer on the stack.
  //
  sq_pushbool(m_vm, SQTrue);
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1), "");
  sq_poptop(m_vm);
#endif
}

//----------------------------------------------------------------------------------------------------------------------
template<typename TypeParam>
class FloatStackUtilsTest : public BaseTypeFixture<TypeParam> {};
TYPED_TEST_CASE(FloatStackUtilsTest, FloatTypes);

//----------------------------------------------------------------------------------------------------------------------
TYPED_TEST(FloatStackUtilsTest, TestPush)
{
  // test pushing a randomly generated value
  //
  const TypeParam expected = GetRandomValue();
  EXPECT_SQ_SUCCEEDED(m_vm, sqb::Push(SquirrelFixture::m_vm, expected));

  EXPECT_EQ(1, sq_gettop(SquirrelFixture::m_vm));
  EXPECT_EQ(OT_FLOAT, sq_gettype(SquirrelFixture::m_vm, -1));

  // ensure that the actual value isn't equal to the expected one to start with
  //
  SQFloat actual = static_cast<SQFloat>(expected * 2.0);
  EXPECT_SQ_SUCCEEDED(m_vm, sq_getfloat(SquirrelFixture::m_vm, -1, &actual));
  EXPECT_TYPE_PARAM_EQ(static_cast<SQFloat>(expected), actual);

  sq_poptop(SquirrelFixture::m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TYPED_TEST(FloatStackUtilsTest, TestMatch)
{
  // nothing on the stack so match should fail.
  //
#if SQBIND_ASSERTS_ENABLED
  EXPECT_DEATH(sqb::Match(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1), "");
#endif

  SQFloat value = static_cast<SQFloat>(GetRandomValue());

  // an integer on the stack to match should pass.
  //
  sq_pushfloat(m_vm, value);
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1));
  sq_poptop(m_vm);

  // a boolean on the stack to match should fail.
  //
  sq_pushbool(m_vm, SQTrue);
  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1));
  sq_poptop(m_vm);

  // an integer point number on the stack to match should pass.
  //
  sq_pushinteger(m_vm, 10);
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1));
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TYPED_TEST(FloatStackUtilsTest, TestGet)
{
#if SQBIND_ASSERTS_ENABLED
  // if asserts are on and there is nothing at the index specified then Get should assert.
  //
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1), "");
#endif

  const SQFloat expected = static_cast<SQFloat>(GetRandomValue());

  // Get should succeed if there is an integer on the stack.
  //
  sq_pushfloat(m_vm, expected);
  EXPECT_TYPE_PARAM_EQ(expected, static_cast<SQFloat>(sqb::Get(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1)));
  sq_poptop(m_vm);

  //
  SQInteger expected_int = static_cast<SQInteger>(expected);
  sq_pushinteger(m_vm, expected_int);
  EXPECT_EQ(expected_int, static_cast<SQInteger>(sqb::Get(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1)));
  sq_poptop(m_vm);

#if SQBIND_ASSERTS_ENABLED
  // Get should assert if there is something other than an integer on the stack.
  //
  sq_pushbool(m_vm, SQTrue);
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<TypeParam>(), SquirrelFixture::m_vm, -1), "");
  sq_poptop(m_vm);
#endif
}

//----------------------------------------------------------------------------------------------------------------------
/// string stack utils tests
//----------------------------------------------------------------------------------------------------------------------
typedef SquirrelFixture StringStackUtilsTest;

//----------------------------------------------------------------------------------------------------------------------
TEST_F(StringStackUtilsTest, TestPush)
{
  // actual will always be the opposite of the expected value before sq_getbool is called.
  //
  const SQChar* const expected = _SC("test string");

  // test pushing a string.
  //
  EXPECT_SQ_SUCCEEDED(m_vm, sqb::Push(m_vm, expected));

  EXPECT_EQ(1, sq_gettop(m_vm));
  EXPECT_EQ(OT_STRING, sq_gettype(m_vm, -1));

  const SQChar* actual = _SC("");
  EXPECT_SQ_SUCCEEDED(m_vm, sq_getstring(m_vm, -1, &actual));
  EXPECT_STREQ(expected, actual);

  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(StringStackUtilsTest, TestMatch)
{
  // nothing on the stack so match should fail.
  //
#if SQBIND_ASSERTS_ENABLED
  EXPECT_DEATH(sqb::Match(sqb::TypeWrapper<const SQChar*>(), m_vm, -1), "");
#endif

  // a string on the stack to match should pass.
  //
  sq_pushstring(m_vm, _SC("test string"), -1);
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<const SQChar*>(), m_vm, -1));
  sq_poptop(m_vm);

  // a floating point number on the stack to match should fail.
  //
  sq_pushfloat(m_vm, 0.5f);
  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<const SQChar*>(), SquirrelFixture::m_vm, -1));
  sq_poptop(m_vm);

  // an integer on the stack to match should fail.
  //
  sq_pushinteger(m_vm, 10);
  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<const SQChar*>(), m_vm, -1));
  sq_poptop(m_vm);

  // an bool on the stack to match should fail.
  //
  sq_pushbool(m_vm, SQTrue);
  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<const SQChar*>(), m_vm, -1));
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(StringStackUtilsTest, TestGet)
{
#if SQBIND_ASSERTS_ENABLED
  // if asserts are on and there is nothing at the index specified then Get should assert.
  //
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<const SQChar*>(), m_vm, -1), "");
#endif

  // Get should succeed if there is a boolean on the stack.
  //
  sq_pushstring(m_vm, _SC("test string"), -1);
  EXPECT_STREQ(_SC("test string"), sqb::Get(sqb::TypeWrapper<const SQChar*>(), m_vm, -1));
  sq_poptop(m_vm);

#if SQBIND_ASSERTS_ENABLED
  // Get should die if there is something other than a bool on the stack.
  //
  sq_pushinteger(m_vm, 10);
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<const SQChar*>(), m_vm, -1), "");
  sq_poptop(m_vm);
#endif
}

//----------------------------------------------------------------------------------------------------------------------
/// squirrel object stack utils tests
//----------------------------------------------------------------------------------------------------------------------
typedef SquirrelFixture HSQOBJECTStackUtilsTest;

//----------------------------------------------------------------------------------------------------------------------
TEST_F(HSQOBJECTStackUtilsTest, TestPush)
{
  sq_pushinteger(m_vm, kExpectedInteger);
  HSQOBJECT object;
  sq_getstackobj(m_vm, -1, &object);
  sq_addref(m_vm, &object);
  sq_poptop(m_vm);

  // test pushing the object.
  //
  EXPECT_SQ_SUCCEEDED(m_vm, sqb::Push(m_vm, object));

  EXPECT_EQ(1, sq_gettop(m_vm));
  EXPECT_EQ(sq_type(object), sq_gettype(m_vm, -1));

  sq_poptop(m_vm);

  sq_release(m_vm, &object);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(HSQOBJECTStackUtilsTest, TestMatch)
{
  // nothing on the stack so match should fail.
  //
#if SQBIND_ASSERTS_ENABLED
  EXPECT_DEATH(sqb::Match(sqb::TypeWrapper<HSQOBJECT>(), m_vm, -1), "");
#endif

  // a string on the stack to match should pass.
  //
  sq_pushstring(m_vm, _SC("test string"), -1);
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<HSQOBJECT>(), m_vm, -1));
  sq_poptop(m_vm);

  // a floating point number on the stack to match should pass.
  //
  sq_pushfloat(m_vm, 0.5f);
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<HSQOBJECT>(), SquirrelFixture::m_vm, -1));
  sq_poptop(m_vm);

  // an integer on the stack to match should pass.
  //
  sq_pushinteger(m_vm, 10);
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<HSQOBJECT>(), m_vm, -1));
  sq_poptop(m_vm);

  // a bool on the stack to match should pass.
  //
  sq_pushbool(m_vm, SQTrue);
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<HSQOBJECT>(), m_vm, -1));
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(HSQOBJECTStackUtilsTest, TestGet)
{
#if SQBIND_ASSERTS_ENABLED
  // if asserts are on and there is nothing at the index specified then Get should assert.
  //
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<HSQOBJECT>(), m_vm, -1), "");
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<HSQOBJECT>(), m_vm, 0), "");
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<HSQOBJECT>(), m_vm, 1), "");
#endif

  // Get should succeed if there is anything on the stack.
  //
  sq_pushstring(m_vm, _SC("test string"), -1);
  HSQOBJECT actual = sqb::Get(sqb::TypeWrapper<HSQOBJECT>(), m_vm, -1);
  EXPECT_EQ(OT_STRING, sq_type(actual));
  EXPECT_STREQ(_SC("test string"), sq_objtostring(&actual));
  sq_poptop(m_vm);

  // Get should succeed if there is anything on the stack.
  //
  sq_pushinteger(m_vm, 10);
  actual = sqb::Get(sqb::TypeWrapper<HSQOBJECT>(), m_vm, -1);
  EXPECT_EQ(OT_INTEGER, sq_type(actual));
  EXPECT_EQ(10, sq_objtointeger(&actual));
  sq_poptop(m_vm);
}
